package com.lty.springmvc.handler;

import java.io.IOException;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.servlet.ModelAndView;

import com.lty.springmvc.entity.User;

@Controller
@RequestMapping("springmvc")
@SessionAttributes(value = "user", types = String.class)
public class SpringMVCTest {
	private static final String SUCCESS = "success";
	
	/***************************RequestMapping Test**************************/
	/***
	 * 1、@RequestMapping除了可以修饰方法，还可以修饰类
	 * 2、
	 * 	1) 类定义处：提供初步的请求映射信息。相对于 WEB 应用的根目录 – 
	 *  2) 方法处：提供进一步的细分映射信息。相对于类定义处的 URL。若 类定义处未标注 @RequestMapping，则方法处标记的 URL 相对于 WEB 应用的根目录
	 * 
	 * @return
	 */
	@RequestMapping("/testRequestMapping")
	public String testRequestMapping(){
		System.out.println("testRequestMapping");
		return SUCCESS;
	}
	
	/***
	 * 常用：
	 * 使用method属性来指定请求方式
	 * @return
	 */
	@RequestMapping(value="/testMethod", method = RequestMethod.POST)
	public String testMethod(){
		System.out.println("testMethod");
		return SUCCESS;
	}
	
	/***
	 * 
	 * 了解：可以使用params和headers来更加精确地映射请求，params和headers支持简单的表达式。
	 * 
	 * @return
	 */
//	@RequestMapping(value = "testParamsAndHeaders", params = {"username","age!=10"}, headers = {"Accept-Language=zh-CN,zh;q=0.8"})
	@RequestMapping(value = "testParamsAndHeaders", params = "username", headers = {"Accept-Language=zh-CN,zh;q=0.8"})
//	@RequestMapping(value = "testParamsAndHeaders", params = {"username","age!=10"}, headers = {"Accept-Language=en-US,zh;q=0.8"})
	public String testParamsAndHeaders(){
		System.out.println("testParamsAndHeaders");
		return SUCCESS;
	}
	
	/***************************Ant Test**************************/
	/***
	 * 了解：
	 * @RequestMapping支持AntPath通配符
	 * 
	 * @return
	 */
	@RequestMapping("/testAntPath/*/abc")
	public String testAntPath(){
		System.out.println("testAntPath");
		return SUCCESS;
	}
	
	/***************************REST PathVariable Test**************************/
	
	/***
	 * 
	 * @PathVariable 可以用来映射URL中的占位符到目标方法的参数中
	 * 
	 * @param id1
	 * @return
	 */
	@RequestMapping("/testPathVariable/{id}")
	public String testPathVariable(@PathVariable("id") Integer id1){
		System.out.println("testPathVariable1: " + id1);
		return SUCCESS;
	}
	
	/***************************REST Test**************************/
	/*****
	 * REST 风格的url
	 * 以CRUD为例：
	 * 1、新增：/order POST
	 * 2、修改：/order/1 PUT  	    update?id=1
	 * 3、获取：/order/1 GET  	    get?id=1
	 * 4、删除：/order/1 DELETE    delete?id=1
	 * 
	 * 如何发送PUT请求或DELETE请求？
	 * 1、需要配置HiddenHttpMethodFilter
	 * 2、需要发送POST请求
	 * 3、在发送POST请求的同时，携带一个name="_method"的隐藏域，值为PUT或DELETE
	 * 
	 * 在SpringMVC的目标方法中如何得到id呢？
	 * 使用@PathVariable 注解
	 * 
	 * @param id
	 * @return
	 */
	@RequestMapping(value = "/testRest/{id}", method = RequestMethod.GET)
	public String testRest(@PathVariable("id") Integer id){
		System.out.println("test Rest GET: " + id);
		return SUCCESS;
	}
	
	@RequestMapping(value = "/testRest", method = RequestMethod.POST)
	public String testRest(){
		System.out.println("test Rest POST");
		return SUCCESS;
	}
	
	@RequestMapping(value = "/testRest/{id}", method = RequestMethod.DELETE)
	public String testRestDelete(@PathVariable("id") Integer id){
		System.out.println("test Rest DELETE: " + id);
		return SUCCESS;
	}
	
	@RequestMapping(value = "/testRest/{id}", method = RequestMethod.PUT)
	public String testRestPut(@PathVariable("id") Integer id){
		System.out.println("test Rest PUT: " + id);
		return SUCCESS;
	}
	
	/***************************RequestParam Test******************************/
	/***
	 * @RequestParam 映射请求参数
	 * value值即请求参数的参数名
	 * required该参数是否必须(默认为true)
	 * defaultValue请求参数的默认值
	 * 
	 * @param username1
	 * @param age1
	 * @return
	 */
	@RequestMapping("/testRequestParam")
	public String testRequestParam(@RequestParam(value="username", required = false) String username1,
			@RequestParam(value = "age",required = false, defaultValue = "0") int age1){
		System.out.println("testRequestParam,username: " + username1 + ",age: " + age1);
		return SUCCESS;
	}

	/***************************RequestHeader Test******************************/
	/***
	 * 了解:
	 * 映射请求头信息
	 * 用法同@RequestParam
	 * 
	 * @param lan
	 * @return
	 */
	@RequestMapping("/testRequestHeader")
	public String testRequestHeader(@RequestHeader(value="Accept-Language") String lan){
		System.out.println("testRequestHeader: " + lan);
		return SUCCESS;
	}
	
	/***************************CookieValue Test******************************/
	/***
	 * 了解：
	 * @CookieValue:映射一个cookie值
	 * 用法同@RequestParam
	 * 
	 * @param jSessionId
	 * @return
	 */
	@RequestMapping("/testCookieValue")
	public String testCookieValue(@CookieValue("JSESSIONID") String jSessionId){
		System.out.println("testCookieValue: " + jSessionId);
		return SUCCESS;
	}
	
	/***************************POJO Test******************************/
	/***
	 * Spring MVC 会按请求参数名和 POJO 属性名进行自动匹配，
	 * 自动为该对象填充属性值。支持级联属性。
	 * 如：dept.deptId、dept.address.tel 等
	 * 
	 * @param user
	 * @return
	 */
	@RequestMapping("testPojo")
	public String testPojo(User user){
		System.out.println("testPojo: " + user);
		return SUCCESS;
	}
	
	/***************************ServletAPI Test******************************/
	/***
	 * 可以使用Servlet原生的API作为目标方法的参数
	 * 具体支持以下类型：
	 * HttpServletRequest
	 * HttpServletResponse
	 * HttpSession
	 * java.security.Principal
	 * Locale
	 * InputStream
	 * OutputStream
	 * Reader
	 * Writer
	 * 
	 * @param request
	 * @param response
	 * @return
	 * @throws IOException 
	 */
	@RequestMapping("/testServletAPI")
	public String testServletAPI(HttpServletRequest request, HttpServletResponse response){
		System.out.println("testServletAPI: " + request + ", " + response);
		return SUCCESS;
	}
	
//	@RequestMapping("/testServletAPI")
//	public void testServletAPI(//HttpServletRequest request, HttpServletResponse response, 
//			Writer out) throws IOException{
//		out.write("Hello SpringMVC!");
//	}
	
	/***************************ModelAndView Test******************************/
	/***
	 * 目标方法的返回值可以是ModelAndView类型，
	 * 其中可以包含视图和模型数据。
	 * 
	 * SpringMVC会把ModelAndView的model(ModelMap)中的数据放入到Request域对象中
	 * @return
	 */
	@RequestMapping("/testModelAndView")
	public ModelAndView testModelAndView(){
		String viewName = SUCCESS;
		ModelAndView modelAndView = new ModelAndView(viewName);
		
		//添加模型数据到modelAndView中
		modelAndView.addObject("time", new Date());
		
		return modelAndView;
	}
	
	/***************************Map Test******************************/
	/***
	 * 目标方法入参可以添加Map类型(实际上也可以是Model类型或ModelMap类型)的参数。
	 * 
	 * @param map
	 * @return
	 */
	@RequestMapping("/testMap")
	public String testMap(Map<String,Object> map){
		System.out.println(map.getClass().getName());
		map.put("names",Arrays.asList("Tom","Jerry","Jack"));
		return SUCCESS;
	}
	
	/***************************SessionAttributes Test******************************/
	/***
	 * @SessionAttributes 除了可以通过属性名指定需要放到会话中的属性外(实际上使用的是value(字符串数组)属性值)，
	 * 还可以通过模型属性的对象类型指定哪些模型属性需要放到会话中(实际上使用的是types(类类型属性值)属性值)
	 * 
	 * 注：该注解只能放在类上，而不能修饰方法
	 * 
	 * @param map
	 * @return
	 */
	@RequestMapping("/testSessionAttributes")
	public String testSessionAttributes(Map<String, Object> map){
		User user = new User("Tom", "123", "tom@163.com", 15);
		map.put("user", user);
		map.put("school", "lty");
		return SUCCESS;
	}
	
	/***************************ModelAttribute Test******************************/
	/***
	 * 运行流程：
	 * 1、执行@ModelAttribute注解修饰的方法：从数据库取出对象，把对象放入Map中，键为：user；
	 * 2、SpringMVC从Map中取出User对象，并将表单的请求参数赋给该User对象的对应属性；
	 * 3、SpringMVC将上述User对象传入目标方法的入参
	 * 
	 * 注意：在@ModelAttribute修饰的方法中，放入到Map中的键需要和目标方法入参类型的第一个字面小写的字符串一致！
	 * 
	 * 源代码分析的流程：
	 * 1、调用@ModelAttribute注解修饰的方法，实际上把@ModelAttribute方法中Map中的数据放在了implicitModel中；
	 * 2、解析请求处理器的目标参数，实际上该参数来自于WebDataBinder对象的target属性；
	 *  1> 创建WebDataBinder对象：
	 *    ① 确定objectName属性：若传入的attrName属性值为"",则objectName为类名第一个字母小写；
	 *    **注意：attrName，若目标方法的POJO属性使用了@ModelAttribute来修饰，则attrName值即为@ModelAttribute的value属性值；
	 *    
	 *    ② 确定target属性：
	 * 	  > 在implicitModel中查找attrName对应的属性值，若查找，ok；
	 * 	  > **若不存在：则验证当前Handler是否使用了@SessionAttributes进行修饰，若使用了，则尝试从Session中获取attrName所对应的属性值，若Session中没有对应的值，则抛出异常；
	 * 	  > 若Handler没有使用@SessionAttributes进行修饰，或@SessionAttributes中没有使用value值指定的key和attrName相匹配，则通过反射创建POJO对象。
	 *
	 * 	 2> SpringMVC把表单的请求参数赋给了WebDataBinder的target属性。
	 *   3> **SpringMVC会把WebDataBinder的attrName和target给到implicitModel，继而传到request域对象中。
	 *   4> 把WebDataBinder的target作为参数传递给目标方法的入参。
	 * 
	 * 
	 * 小结：
	 * SpringMVC确定目标方法POJO类型入参的过程：
	 * 1、确定一个key：
	 * 	  1> 若目标方法的POJO类型的参数没有@ModelAttribute作为修饰，则key为POJO类名第一个字母小写；
	 * 	  2> 若使用了@ModelAttribute来修饰，则key为@ModelAttribute注解的value属性值。
	 * 2、在implicitModel中查找key对应的对象，若存在，则作为入参传入
	 * 3、若在implicitModel中不存在key对应的对象，则检查当前Handler是否使用了@SessionAttributes注解修饰，
	 * 若使用了该注解，且@SessionAttributes注解的value值包含了key，则会从HttpSession中来获取key对应的value值，若存在
	 * 则直接传入目标方法的入参中，否则抛出异常。
	 * 4、若Handler没有标识@SessionAttributes注解或@SessionAttributes注解的value值中不包括key，则SpringMVC会通过反射来创建POJO类型的参数，传入目标方法的入参
	 * 5、SpringMVC会把key和POJO类型的对象保存到implicitModel中，进而保存到request域对象中。
	 * (其实这中间还有请求表单数据的赋值过程...)
	 * @param user
	 * @return
	 */
	@RequestMapping("/testModelAttribute")
//	public String testModelAttribute(@ModelAttribute("mnxyz") User user){
	public String testModelAttribute(@ModelAttribute("user") User user){
		System.out.println("修改：" + user);
		return SUCCESS;
	}
	
	/***
	 * 
	 * 1、有@ModelAttribute标记的方法，会在每个目标方法执行之前被SpringMVC调用
	 * 2、@ModelAttribute也可以修饰目标方法POJO类型的入参，其value属性值有如下作用：
	 * 	 1> SpringMVC会使用value属性值在implicitModel中查找对应的对象，若存在则会直接传入到目标方法的入参中；
	 * 	 2> SpringMVC会以key，POJO类型的对象为value，存入request域对象中。
	 * 
	 * @param id
	 * @param map
	 */
	@ModelAttribute
	public void getUser(@RequestParam(value = "id", required = false) Integer id, Map<String, Object> map){
		System.out.println("ModelAttribute method...");
		if(id != null){
			//模拟从数据库获取对象
			User user = new User(1, "Tom", "123", "tom@163.com", 12);
			System.out.println("从数据库获取一个对象：" + user);
//			map.put("abc", user);
			//该User对象也会传入到request请求域中！！！
			map.put("user", user);
		}
	}
	
	/***************************ModelAttribute Test******************************/
	@RequestMapping("/testViewAndViewResolver")
	public String testViewAndViewResolver(){
		System.out.println("testViewAndViewResolver");
		return SUCCESS;
	}

	/***************************CustomView Test******************************/
	@RequestMapping("/testCustomView")
	public String testCustomView(){
		System.out.println("testCustomView");
		return "helloView";
	}
	
	/***************************RedirectView Test******************************/
	@RequestMapping("/testRedirectView")
	public String testRedirectView(){
		System.out.println("testRedirectView");
		return "redirect:/index.jsp";
	}
	
	@RequestMapping("/testRedirectView2")
	public String testRedirectView2(){
		System.out.println("testRedirectView2");
		return "redirect:testCustomView";
	}
	
	@RequestMapping("/testForwardView")
	public String testForwardView(){
		System.out.println("testForwardView");
		return "forward:testCustomView";
	}
	
	@RequestMapping("/testForwardView2")
	public String testForwardView2(){
		System.out.println("testForwardView2");
		return "forward:/WEB-INF/view/success.jsp";
	}
}
